package download

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"

	"hundsun.com/hsl/hschain/common/util"

	"github.com/golang/protobuf/proto"
	"github.com/google/uuid"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/p2p/common/constant"
	pbcommon "hundsun.com/hsl/hschain/protos/common"
	pbledger "hundsun.com/hsl/hschain/protos/ledger"
	pbp2p "hundsun.com/hsl/hschain/protos/p2p"
	"hundsun.com/hsl/hschain/store/mq"
)

func (p *Protocol) handleStreamDownloadBlock(msg *pbp2p.P2PMsg) *pbp2p.P2PMsg {
	var req = &pbp2p.GetBlocksRequest{}
	_, err := util.Bytes2Struct(req, msg.Payload)
	if err != nil {
		p.Log.Error("Handle", "err", err)
		return nil
	}
	//允许下载的最大高度区间为20
	if req.End-req.Start > 20 || req.End < req.Start {
		p.Log.Error("handleStreamDownloadBlock", "error", "wrong parameter")
		return nil
	}

	reqBlock := &pbcommon.ReqBlocks{
		Start: req.Start,
		End:   req.End,
	}
	msgBlock := p.WorkClient.NewMessage(types.BlockChainModule, mq.ModeWorker.String(), types.EventGetBlocks, reqBlock)
	if err := p.WorkClient.Send(msgBlock, true); err != nil {
		p.Log.Errorf("get block from blockchain request failed reason:%v", err)
		return nil
	}
	queryResp, err := p.WorkClient.WaitTimeout(msgBlock, time.Duration(5)*time.Second)
	if err != nil {
		errReturn := fmt.Errorf("get block failed, reason:%v", err)
		return p.P2pNode.NewP2pMessage("", "", constant.MsgFailed, []byte(errReturn.Error()), nil)
	}
	blks := queryResp.Data.([]*pbledger.Block)
	resp := &pbp2p.GetBlocksResponse{
		Blocks: blks,
	}
	buf, _ := proto.Marshal(resp)
	return p.P2pNode.NewP2pMessage("", "", constant.MsgGetBlock, buf, nil)
}

func (p *Protocol) handleEventDownloadBlock(msg *mq.Message) {
	req := msg.Data.(*pbp2p.GetBlocksRequest)
	if req.GetStart() > req.GetEnd() {
		p.Log.Error("handleEventDownloadBlock", "download start", req.GetStart(), "download end", req.GetEnd())
		return
	}

	taskID := uuid.New().String() + "+" + fmt.Sprintf("%d-%d", req.GetStart(), req.GetEnd())

	p.Log.Debug("handleEventDownloadBlock", "taskID", taskID, "download start", req.GetStart(), "download end", req.GetEnd())

	//具体的下载逻辑
	jobS := p.initJob(taskID)
	p.Log.Debug("handleEventDownloadBlock", "jobs", jobS)
	var wg sync.WaitGroup
	var mutex sync.Mutex
	var maxGoroutine int32
	var reDownload = make(map[string]interface{})
	var startTime = time.Now().UnixNano()

	// TODO 后面优化性能情况下，可以每次可以向不同节点请求多个区块
	for height := req.GetStart(); height <= req.GetEnd(); height++ {
		wg.Add(1)
	Wait:
		if atomic.LoadInt32(&maxGoroutine) > 50 {
			time.Sleep(time.Millisecond * 200)
			goto Wait
		}
		atomic.AddInt32(&maxGoroutine, 1)
		go func(blockheight uint64, tasks tasks) {
			err := p.downloadBlock(blockheight, tasks)
			if err != nil {
				mutex.Lock()
				defer mutex.Unlock()

				if err == p.Ctx.Err() {
					p.Log.Errorf("syncDownloadBlock failed, reason:%v", err.Error())
					return
				}

				p.Log.Errorf("syncDownloadBlock failed, reason:%v", err.Error())
				v, ok := reDownload[taskID]
				if ok {
					failedJob := v.(map[uint64]bool)
					failedJob[blockheight] = false
					reDownload[taskID] = failedJob

				} else {
					var failedJob = make(map[uint64]bool)
					failedJob[blockheight] = false
					reDownload[taskID] = failedJob

				}
			}
			wg.Done()
			atomic.AddInt32(&maxGoroutine, -1)

		}(height, jobS)
	}

	wg.Wait()
	p.checkTask(taskID, reDownload)
	p.Log.Debugf("Download Job Complete, TaskID:%s, cost time:%s", taskID, fmt.Sprintf("cost time:%d ms", (time.Now().UnixNano()-startTime)/1e6))
}
